Android 视图体系结构的秘密
hi
这是 dhl
的第 41
篇文章
android-10.0.0_r14
收录于 https://github.com/hi-dhl/Android10-Source-Analysis
通过这篇文章你将学习到以下内容,将在文末总结部分会给出相应的答案
Activity 和 Dialog 视图解析绑定过程? Activity 的视图如何与 Window 关联的? Window 如何与 WindowManager 关联? Dialog 的视图如何与 Window 关联?
本文主要分析 Activity 、Window 、PhoneWindow 、WindowManager 之间的关系,为我们后面的文章 「如何在 Andorid 系统里添加自定义View」 等等文章奠定基础,先来了解一下它们的基本概念
Activity:应用视图的容器。 WindowManager:它是一个接口类,继承自接口 ViewManager,对 Window 进行管理 Window:它是一个抽象类,它作为一个顶级视图添加到 WindowManager 中,对 View 进行管理 PhoneWindow:Window 唯一实现类,Window 是一个抽象概念,添加到WindowManager 的根容器 DecorView: 它是 PhoneWindow 内部的一个成员变量,继承自 FrameLayout,FrameLayout 继承自 ViewGroup
在分析他们之前的关系之前,我们先来回顾一下 Acivity 和 Dialog 视图解析绑定的过程
Acivity 和 Dialog 视图解析绑定的过程
在之前的文章 Android 资源加载源码分析一 中介绍了 Acivity 的解析和绑定,总的来说分为三步
调用 LayoutInflater 的 inflate
方法,深度优先遍历解析 View调用 ViewGroup 的 addView
方法将子 View 添加到根布局中调用 WindowManager 的 addView
方法添加根布局
LayoutInflater 的 inflate
方法有多个重载的方法,常用的是下面三个参数的方法
frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
resource
:要解析的 xml 布局文件 Idroot
:表示根布局attachToRoot
:是否要添加到父布局 root 中
resource
其实很好理解就是资源 Id,而 root
和 attachToRoot
分别代表什么意思:
当 attachToRoot == true
且root != null
时,新解析出来的 View 会被 add 到 root 中去,然后将 root 作为结果返回当 attachToRoot == false
且root != null
时,新解析的 View 会直接作为结果返回,而且 root 会为新解析的 View 生成 LayoutParams 并设置到该 View 中去当 attachToRoot == false
且root == null
时,新解析的 View 会直接作为结果返回
当 View 解析完成之后,最后会调用 WindowManager 的 addView
方法,WindowManager 是一个接口类,继承自接口 ViewManager,用来管理 Window,它的实现类为 WindowManagerImpl,所以调用 WindowManager 的 addView
方法,实际上调用的是 WindowManagerImpl 的 addView
方法
frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
@UnsupportedAppUsage
// 单例的设计模式
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
......
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// mGlobal 是 WindowManagerGlobal 的实例
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
......
}
mGlobal 是 WindowManagerGlobal 的实例,使用的单例设计模式,参数 mParentWindow 是 Window 的实例,实际上是委托给 WindowManagerGlobal 去实现的
到这里我们关于 Acivity 视图的解析和添加过程大概介绍完了,接下来分析一下 Activity 、Window 、WindowManager 的关系
Activity 、Window 、WindowManager 的关系
在 Activity 内部维护着一个 Window 的实例变量 mWindow
frameworks/base/core/java/android/app/Activity.java
public class Activity extends ContextThemeWrappe{
private Window mWindow;
}
Window 是一个抽象类,它的具体实现类为 PhoneWindow,在 Activity 的 attach 方法中给 Window 的实例变量 mWindow 赋值
frameworks/base/core/java/android/app/Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
......
mWindow = new PhoneWindow(this, window, activityConfigCallback);
......
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
......
}
创建了 PhoneWindow 并赋值给 mWindow 调用 PhoneWindow 的 setWindowManager
方法,这个方法的具体实现发生在 Window 中,最终调用的是 Window 的setWindowManager
方法
frameworks/base/core/java/android/view/Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
......
// mWindowManager 是 WindowManagerImpl的实例变量
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
将 WindowManager 转换为 WindowManagerImpl,之后调用 createLocalWindowManager 方法,并传递当前的 Window 对象,构建 WindowManagerImpl 对象,之后赋值给 mWindowManager
frameworks/base/core/java/android/view/WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
其实在 createLocalWindowManager
方法中,就做了一件事,将 Window 作为参数构建了一个 WindowManagerImpl 对象返还给调用处
总的来说,其实就是在 Activity 的 attach
方法中,通过调用 Window 的 setWindowManager
方法将 Window 和 WindowManager 关联在了一起
PhoneWindow 是 Window 的实现类,它是一个窗口,本身并不具备 View 相关的能力,实际上在 PhoneWindow 内部维护这一个变量 mDecor
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window{
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 完成DecorView的实例化
mDecor = generateDecor(-1);
......
}
if (mContentParent == null) {
// 调用 generateLayout 方法 要负责了DecorView的初始设置,诸如主题相关的feature、DecorView的背景
mContentParent = generateLayout(mDecor);
}
......
}
// 完成DecorView的实例化
protected DecorView generateDecor(int featureId) {
......
return new DecorView(context, featureId, this, getAttributes());
}
// 调用 generateLayout 方法 要负责了DecorView的初始设置,
// 诸如主题相关的feature、DecorView的背景,同时也初始化 contentParent
protected ViewGroup generateLayout(DecorView decor) {
......
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
}
}
mDecor 是 window 的顶级视图,它继承自 FrameLayout,它的创建过程由 installDecor
完成,然后在installDecor
方法中通过generateDecor
方法来完成DecorView的实例化调用 generateLayout
方法 要负责了 DecorView 的初始设置,诸如主题相关的 feature 、DecorView 的背景,同时也初始化 contentParentmDecor 它实际上是一个 ViewGroup,当在 Activity 中调用 setContentView
方法,通过调用inflater
方法把布局资源转换为一个 View,然后添加到 DecorView 的 mContenParnent 中
当 View 初始化完成之后,最后会进入 ActivityThread 的 handlerResumeActivity
方法,执行了 r.activity.makeVisible()
方法
frameworks/base/core/java/android/app/ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
......
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
......
}
最终调用 Activity 的 makeVisible
方法,把 decorView 添加到 WindowManage 中
frameworks/base/core/java/android/app/Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
到这里他们之间的关系明确了:
一个 Activity 持有一个 PhoneWindow 的对象,而一个 PhoneWindow 对象持有一个 DecorView 的实例 PhoneWindow 继承自 Window,一个 Window 对象内部持有 mWindowManager 的实例,通过调用 setWindowManager
方法与 WindowManager 关联在一起WindowManager 继承自 ViewManager,WindowManagerImpl 是 WindowManager 接口的实现类,但是具体的功能都会委托给 WindowManagerGlobal 来实现 调用 WindowManager 的 addView 方法,实际上调用的是 WindowManagerImpl 的 addView 方法
总结
Acivity 和 Dialog 视图解析绑定过程?
调用 LayoutInflater 的 inflate
方法,深度优先遍历解析 View调用 ViewGroup 的 addView
方法将子 View 添加到根布局中调用 WindowManager 的 addView
方法添加根布局
Activity 的视图如何与 Window 关联的?
在 Activity 内部维护着一个 Window 的实例变量 mWindow
frameworks/base/core/java/android/app/Activity.java
public class Activity extends ContextThemeWrappe{
private Window mWindow;
}
最后调用 Activity 的 makeVisible
方法,把 decorView 添加到 WindowManage 中
frameworks/base/core/java/android/app/Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
Window 如何与 WindowManager 关联?
在 Activity 的 attach
方法中,调用 PhoneWindow 的 setWindowManager
方法,这个方法的具体实现发生在 Window 中,最终调用的是 Window 的 setWindowManager
方法,将 Window 和 WindowManager 关联在了一起
frameworks/base/core/java/android/view/Window.java
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
......
// mWindowManager 是 WindowManagerImpl的实例变量
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
Dialog 的视图如何与 Window 关联?
在 Dialog 的构造方法中初始化了 Window 对象
frameworks/base/core/java/android/app/Dialog.java
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
...
// 获取WindowManager对象
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
// 构建PhoneWindow
final Window w = new PhoneWindow(mContext);
// mWindow 是PhoneWindow实例
mWindow = w;
...
}
调用 Dialog 的 show
方法,完成 view 的绘制和 Dialog 的显示
frameworks/base/core/java/android/app/Dialog.java
public void show() {
// 获取DecorView
mDecor = mWindow.getDecorView();
// 获取布局参数
WindowManager.LayoutParams l = mWindow.getAttributes();
// 将DecorView和布局参数添加到WindowManager中
mWindowManager.addView(mDecor, l);
}
仓库 KtKit 是用 Kotlin 语言编写的小巧而实用的工具库,包含了项目中常用的一系列工具, 正在逐渐完善中,如果你有兴趣,想邀请你和我一起来完善这个库。
KtKit 仓库地址:https://github.com/hi-dhl/KtKit KtKit 在线阅读:https://ktkit.hi-dhl.com
如果这个仓库对你有帮助,请在仓库右上角帮我 star 一下,非常感谢你的支持,同时也欢迎你提交 PR
如果有帮助欢迎 在看 、点赞 、分享 就是对我最大的鼓励
代码不止,文章不停
欢迎点击下方卡片关注我,持续分享最新的技术
推荐阅读:
最后推荐我一直在更新维护的项目和网站:
个人博客,将所有文章进行分类,欢迎前去查看
https://hi-dhl.com最新的 AndroidX Jetpack 相关组件的实战项目 以及 原理分析的文章
https://github.com/hi-dhl/AndroidX-Jetpack-PracticeLeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析
剑指 offer:https://offer.hi-dhl.com
LeetCode:https://leetcode.hi-dhl.com最新 Android 10 源码分析系列文章
https://github.com/hi-dhl/Android10-Source-Analysis一系列国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的分析
https://github.com/hi-dhl/Technical-Article-Translation「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址
https://site.51git.cn